﻿using System;
using System.Collections.Generic;
using System.Text;
using CodePlex.Resourceful;
using System.Security.Cryptography;
using System.IO;
using CodePlex.Resourceful.Amazon.S3;
using System.Xml;

namespace CodePlex.Resourceful.Microsoft.Azure
{


    // If a request specifies both the If-None-Match and If-Modified-Since headers, the request is evaluated based on the criteria specified in If-None-Match.
    // If a request specifies both the If-Match and If-Unmodified-Since headers, the request is evaluated based on the criteria specified in If-Match.
    // With the exception of the two combinations of conditional headers listed above, a request may specify only a single conditional header.


    //Set Container Metadata
    public class Conditions1
    {
        public DateTime? IfModifiedSince { get; set; }
    }
    //Delete Container, Get Blob Properties
    public class Conditions2 : Conditions1
    {
        public DateTime? IfUnmodifiedSince { get; set; }
    }
    // Put Blob, Get Blob, Set Blob Metadata, Put Block List, Delete Blob
    public class Conditions3 : Conditions2
    {
        public string IfMatch { get; set; }
        public string IfNoneMatch { get; set; }
    }


    public class AzureBlobStorageClient : BaseAzureClient
    {

        public static readonly string Version = "2009-07-17";

        public AzureBlobStorageClient(string accountName, string base64key)
            : this(accountName, base64key, true)
        {
        }

        public AzureBlobStorageClient(string accountName, string base64key,bool useSSL)
            : this(accountName, base64key, AzureConstants.PublicBlobServiceHost, false, useSSL)
        {
        }

         public AzureBlobStorageClient(string accountName, string base64key, string serviceHost, bool usePathStyle, bool useSSL)
            : base(accountName, base64key,serviceHost,usePathStyle,useSSL,Version)
         {
     
         }




      


        public Uri GetContainerUri(string containerName)
        {
            string rt = ComputeRootUri(this.AccountName, this.IsUsingPathStyle, this.IsSSL, this.ServiceHost) + containerName;
            return new Uri(rt);
        }


        public Uri GetBlobUri(string containerName, string blobName)
        {
            string rt = ComputeRootUri(this.AccountName, this.IsUsingPathStyle, this.IsSSL, this.ServiceHost) + containerName + "/" + blobName;
            return new Uri(rt);
        }


        // ACCOUNT

        public ClientResponse GetContainers(string prefix, string marker, int? maxResults)
        {
            // GET http://myaccount.blob.core.windows.net?comp=list

            NameValuePairs pathParameters = new NameValuePairs();
            if (prefix != null)
            {
                pathParameters["prefix"] = prefix;
            }
            if (marker != null)
            {
                pathParameters["marker"] = marker;
            }
            if (maxResults != null)
            {
                pathParameters["maxresults"] = maxResults.Value.ToString();
            }

            return this.DoAzureRequest(HttpMethod.Get, "", "list", pathParameters, null);
        }





        // CONTAINER

        public ClientResponse PutContainer(string containerName, NameValuePairs metadata)
        {
            // PUT http://myaccount.blob.core.windows.net/mycontainer?restype=container HttpStatus.Created,

            metadata = metadata ?? new NameValuePairs();

            NameValuePairs headers = new NameValuePairs();
            foreach (string key in metadata.Keys)
            {
                headers.Add(AzureConstants.Headers.MicrosoftMetaPrefix + key, metadata[key]);
            }

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container"; 

            return DoAzureRequest(HttpMethod.Put, containerName, null, pathParameters, headers);
        }
        public ClientResponse SetContainerMetadata(string containerName, NameValuePairs metadata, Conditions1 conditions)
        {
            // PUT http://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=metadata

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";

            return DoAzureRequest(HttpMethod.Put, containerName, "metadata", pathParameters,
                    Combine(MetadataToHeaders(metadata),ConditionsToHeaders(conditions)));
        }

 

        public ClientResponse GetContainerProperties(string containerName)
        {
            // GET/HEAD http://myaccount.blob.core.windows.net/mycontainer?restype=container
            // GET/HEAD return the same output

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";

            return DoAzureRequest(HttpMethod.Get, containerName, null, pathParameters, null);
        }

        public ClientResponse GetContainerACL(string containerName)
        {
            // GET/HEAD http://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=acl

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";

            return DoAzureRequest(HttpMethod.Get, containerName, "acl", pathParameters, null);
        }
        public ClientResponse SetContainerACL(string containerName, bool isPublic)
        {
            //PUT http://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=acl

            NameValuePairs headers = new NameValuePairs();
            headers[AzureConstants.Headers.MicrosoftPropertyPublicAccess] = isPublic.ToString();

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";

            return DoAzureRequest(HttpMethod.Put, containerName, "acl", pathParameters, headers);

        }
        public ClientResponse DeleteContainer(string containerName, Conditions2 conditions)
        {
            // DELETE http://myaccount.blob.core.windows.net/mycontainer?restype=container  HttpStatus.Accepted, 

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";

            return DoAzureRequest(HttpMethod.Delete, containerName, null, pathParameters, ConditionsToHeaders(conditions));
        }

        public ClientResponse GetBlobs(string containerName, string prefix, string marker, string delimiter, int? maxResults)
        {
            // GET http://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list

            // GET http://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list&delimiter=/&maxresults=4&timeout=30   <- TODO timeout??

            NameValuePairs pathParameters = new NameValuePairs();
            pathParameters["restype"] = "container";
            if (prefix != null)
            {
                pathParameters["prefix"] = prefix;
            }
            if (marker != null)
            {
                pathParameters["marker"] = marker;
            }
            if (delimiter != null)
            {
                pathParameters["delimiter"] = delimiter;
            }
            if (maxResults != null)
            {
                pathParameters["maxresults"] = maxResults.Value.ToString();
            }

            return DoAzureRequest(HttpMethod.Get, containerName, "list", pathParameters, null);

        }











        // BLOB

        public ClientResponse PutBlob(string containerName, string blobName, NameValuePairs metadata, Func20<Stream> inputStreamProvider, bool computeMD5, Action<long> bytesTransferred, Conditions3 conditions)
        {
            // PUT http://myaccount.blob.core.windows.net/mycontainer/myblob  HttpStatus.Created, 

            NameValuePairs headers =MetadataToHeaders(metadata);

            if (computeMD5)
            {
                headers[HttpHeader.ContentMD5] = Utils.ToBase64(Utils.ToMD5(inputStreamProvider()));
            }
            headers.AddRange(ConditionsToHeaders(conditions));
            return DoAzureRequest(HttpMethod.Put, containerName + "/" + blobName, null, null, headers, true, inputStreamProvider(), bytesTransferred);
        }

        public ClientResponse GetBlob(string containerName, string blobName, bool bufferOutput, string range, Conditions3 conditions)
        {
            // GET http://myaccount.blob.core.windows.net/mycontainer/myblob

            NameValuePairs headers = new NameValuePairs();
            if (range != null)
            {
                headers[HttpHeader.Range] = range;
            }
            headers.AddRange(ConditionsToHeaders(conditions));

            return DoAzureRequest(HttpMethod.Get, containerName + "/" + blobName, null, null, headers, bufferOutput);

        }

        public ClientResponse HeadBlob(string containerName, string blobName, Conditions2 conditions)
        {
            // HEAD http://myaccount.blob.core.windows.net/mycontainer/myblob

            return DoAzureRequest(HttpMethod.Head, containerName + "/" + blobName, null, null, ConditionsToHeaders(conditions));
        }

        public ClientResponse GetBlobMetadata(string containerName, string blobName)
        {
            // GET/HEAD http://myaccount.blob.core.windows.net/mycontainer/myblob?comp=metadata

            return DoAzureRequest(HttpMethod.Get, containerName + "/" + blobName, "metadata", null, null);
        }

        public ClientResponse PutBlobMetadata(string containerName, string blobName, NameValuePairs metadata, Conditions3 conditions)
        {
            // PUT http://myaccount.blob.core.windows.net/mycontainer/myblob?comp=metadata

            return DoAzureRequest(HttpMethod.Put, containerName + "/" + blobName, "metadata", null, 
                       Combine(MetadataToHeaders(metadata),ConditionsToHeaders(conditions)));
        }

        public ClientResponse DeleteBlob(string containerName, string blobName, Conditions3 conditions)
        {
            // DELETE http://myaccount.blob.core.windows.net/mycontainer/myblob  HttpStatus.Accepted,

            return DoAzureRequest( HttpMethod.Delete, containerName + "/" + blobName, null, null, ConditionsToHeaders(conditions));
        }

        public ClientResponse CopyBlob(string sourceAccount, string sourceContainer, string sourceBlob, Conditions3 sourceConditions,
            string destContainerName, string destBlobName, NameValuePairs destMetadata, Conditions3 destConditions )
        {
            // PUT http://myaccount.blob.core.windows.net/mycontainer/myblob  HttpStatus.Created, 

            NameValuePairs headers = MetadataToHeaders(destMetadata);
            headers[AzureConstants.Headers.MicrosoftCopySource] = string.Format("/{0}/{1}/{2}", sourceAccount, sourceContainer, sourceBlob);
            headers.AddRange(ConditionsToHeaders(destConditions));
            headers.AddRange(SourceConditionsToHeaders(sourceConditions));
            
            return DoAzureRequest(HttpMethod.Put, destContainerName + "/" + destBlobName, null, null, headers, true,null, null);
        }

















        private NameValuePairs SourceConditionsToHeaders(Conditions1 conditions)
        {
            Dictionary<string, string> map = new Dictionary<string, string>();
            map[HttpHeader.IfModifiedSince] = AzureConstants.Headers.MicrosoftSourceIfModifiedSince;
            map[HttpHeader.IfUnmodifiedSince] = AzureConstants.Headers.MicrosoftSourceIfUnmodifiedSince;
            map[HttpHeader.IfMatch] = AzureConstants.Headers.MicrosoftSourceIfMatch;
            map[HttpHeader.IfNoneMatch] = AzureConstants.Headers.MicrosoftSourceIfNoneMatch;

            NameValuePairs rt = new NameValuePairs();
            foreach (KeyValuePair<string, string> nvp in ConditionsToHeaders(conditions))
            {
                rt.Add(map[nvp.Key], nvp.Value);
            }
            return rt;
        }

        private NameValuePairs ConditionsToHeaders(Conditions1 conditions)
        {
            NameValuePairs rt = new NameValuePairs();
            if (conditions == null)
                return rt;

            if (conditions.IfModifiedSince.HasValue)
            {
                rt[HttpHeader.IfModifiedSince] = Utils.ToHttpDate(conditions.IfModifiedSince.Value);
            }

            Conditions2 conditions2 = conditions as Conditions2;
            if (conditions2 == null)
                return rt;

            if (conditions2.IfUnmodifiedSince.HasValue)
            {
                rt[HttpHeader.IfUnmodifiedSince] = Utils.ToHttpDate(conditions2.IfUnmodifiedSince.Value);
            }

            Conditions3 conditions3 = conditions as Conditions3;
            if (conditions3 == null)
                return rt;

            if (conditions3.IfMatch != null)
            {
                rt[HttpHeader.IfMatch] = conditions3.IfMatch;
            }
            if (conditions3.IfNoneMatch != null)
            {
                rt[HttpHeader.IfNoneMatch] = conditions3.IfNoneMatch;
            }

            return rt;
        }


        private NameValuePairs Combine(NameValuePairs lhs, NameValuePairs rhs)
        {
            NameValuePairs rt = new NameValuePairs();
            rt.AddRange(lhs);
            rt.AddRange(rhs);
            return rt;
        }



    }
}
